﻿using System;
using System.IO;
using System.Collections.Generic;

namespace tuimg
{
    using tuimg.element;
    using tuimg.helpers;
    using tuimg.structs;

    public class TuimgIco
    {
        internal const uint ico_tag = 0x00010000;
        internal struct_ico_head head;
        internal List<struct_ico_image> images;
        public TuimgIco(string path)
        {
            using (var fr = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                head = read_head(fr);
                var dir_entrys = read_all_dir_entry(fr);
                images = read_all_image(fr, dir_entrys);
            }
        }
        public TuimgIco()
        {
            head = new struct_ico_head();
            head.reserved = 0;
            head.id_type = 1;
            head.id_count = 0;
            images = new List<struct_ico_image>();
        }
        public Stream ToStream()
        {
            var mem = new MemoryStream();
            ToStream(mem);
            return mem;
        }
        public long ToStream(Stream mem)
        {
            var start = mem.Position;
            helper.Write(mem, head.reserved);
            helper.Write(mem, head.id_type);
            head.id_count = (ushort)images.Count;
            helper.Write(mem, head.id_count);

            uint pos = 6;
            pos += (uint)(head.id_count << 4);
            long ix = 0;
            for (var i = 0; i < head.id_count; i++)
            {
                var entry = images[i].entry;
                helper.Write(mem, entry.width);
                helper.Write(mem, entry.height);
                helper.Write(mem, entry.color_count);
                helper.Write(mem, entry.reserved);
                helper.Write(mem, entry.planes);
                helper.Write(mem, entry.bitcount);
                using (var mem_img = read_image_stream(images[i]))
                {
                    entry.res_bytes = (uint)mem_img.Length;
                    entry.image_offset = pos;
                    helper.Write(mem, entry.res_bytes);
                    helper.Write(mem, entry.image_offset);
                    ix = mem.Position;
                    mem.Position = pos;
                    helper.ReadToMemory(mem_img, mem, entry.res_bytes);
                    pos += entry.res_bytes;
                    mem.Position = ix;
                }
            }

            mem.Flush();
            var size = mem.Position - start;
            mem.Seek(0, SeekOrigin.Begin);
            return size;
        }
        public void InsertPng(TuimgPng png)
        {
            var entry = new struct_ico_dir_entry
            {
                bitcount = png.bitcount,
                color_count = 0,
                height = (byte)png.Height,
                width = (byte)png.Width,
                image_offset = 0,
                planes = 1,
                reserved = 0,
                res_bytes = 0,
            };
            images.Add(new struct_ico_image
            {
                entry = entry,
                image = new struct_ico_image_png
                {
                    img = png,
                }
            });
            head.id_count = (ushort)images.Count;
        }
        public void InsertBmp(TuimgPng png)
        {
            var bmp = png.ToBmp(TuimgColor.White);
            var grid = png.ToAndGrid();
            InsertBmp(bmp, grid);
        }
        public void InsertBmp(TuimgBmp bmp, TuimgGrid and)
        {
            var entry = new struct_ico_dir_entry
            {
                bitcount = bmp.infohead.biBitCount,
                color_count = 0,
                height = (byte)bmp.Height,
                width = (byte)bmp.Width,
                image_offset = 0,
                planes = bmp.infohead.biPlanes,
                reserved = 0,
                res_bytes = 0,
            };
            images.Add(new struct_ico_image
            {
                entry = entry,
                image = new struct_ico_image_bmp
                {
                    img = bmp,
                    and = and
                }
            });
            head.id_count = (ushort)images.Count;
        }
        public void Remove(int index)
        {
            images.RemoveAt(index);
            head.id_count = (ushort)images.Count;
        }
        public TuimgPng Read(int index)
        {
            var img = images[index];
            if (img.image is struct_ico_image_png png) return png.img;
            if (img.image is struct_ico_image_bmp bmp) return ToPng(bmp, img.entry);
            return new TuimgPng(1, 1);
        }
        public int ImageCount { get { return head.id_count; } }
        internal struct_ico_head read_head(Stream sr)
        {
            var head = new struct_ico_head { };
            head.reserved = helper.Readushort(sr);
            head.id_type = helper.Readushort(sr);
            head.id_count = helper.Readushort(sr);
            return head;
        }
        internal Stream read_image_stream(struct_ico_image image)
        {
            if (image.image is struct_ico_image_png png) return png.img.ToStream();
            if (image.image is struct_ico_image_bmp bmp) return bmp.img.ToIconStream(bmp.and);
            return Stream.Null;
        }
        internal struct_ico_dir_entry[] read_all_dir_entry(Stream sr)
        {
            var dir_entrys = new struct_ico_dir_entry[head.id_count];

            for (var i = 0; i < head.id_count; i++)
            {
                var dir_entry = new struct_ico_dir_entry { };
                dir_entry.width = helper.Readbyte(sr);
                dir_entry.height = helper.Readbyte(sr);
                dir_entry.color_count = helper.Readbyte(sr);
                dir_entry.reserved = helper.Readbyte(sr);
                dir_entry.planes = helper.Readushort(sr);
                dir_entry.bitcount = helper.Readushort(sr);
                dir_entry.res_bytes = helper.Readuint(sr);
                dir_entry.image_offset = helper.Readuint(sr);

                dir_entrys[i] = dir_entry;
            }
            return dir_entrys;
        }
        internal List<struct_ico_image> read_all_image(Stream sr, struct_ico_dir_entry[] dir_entrys)
        {
            var images = new List<struct_ico_image>(head.id_count);
            for(var i = 0; i < head.id_count; i++)
            {
                sr.Position = dir_entrys[i].image_offset;

                switch (helper.read_image_type(sr)) // 识别并匹配相应 解码
                {
                    case "png":
                        images.Add(read_png(sr, dir_entrys[i]));
                        break;
                    case "bmp":
                    default:
                        images.Add(read_bmp(sr, dir_entrys[i]));
                        break;
                }
            }
            return images;
        }

        internal struct_ico_image read_png(Stream sr, struct_ico_dir_entry entry)
        {
            var img = new struct_ico_image { };
            img.entry = entry;
            img.image = new struct_ico_image_png { img = new TuimgPng(sr) };

            return img;
        }
        internal struct_ico_image read_bmp(Stream sr, struct_ico_dir_entry entry)
        {
            var img = new struct_ico_image { };
            img.entry = entry;
            var ico_bmp = new struct_ico_image_bmp{ };
            ico_bmp.img = new TuimgBmp(sr, true);

            using (var img_and_data = new MemoryStream())
            {
                helper.ReadToMemory(sr, img_and_data, calc_bmp_and_len(entry));
                img_and_data.Seek(0, SeekOrigin.Begin);
                ico_bmp.and = new TuimgGrid(img_and_data, entry.height, entry.width);
                ico_bmp.and.Flip_horizontal();
            }
            img.image = ico_bmp;

            return img;
        }

        internal TuimgPng ToPng(struct_ico_image_bmp ico_bmp, struct_ico_dir_entry entry)
        {
            var png = new TuimgPng(entry.width, entry.height);
            for (var y = 0; y < entry.height; y++)
            {
                for (var x = 0; x < entry.width; x++)
                {
                    if (!ico_bmp.and.Read(x, y))
                    {
                        png.brush_goto(x, y);
                        ico_bmp.img.brush_goto(x, y);
                        var xx = ico_bmp.img.ReadColor();

                        png.WriteColor(xx);
                    }
                }
            }

            return png;
        }
        internal uint calc_bmp_xor_len(struct_bmp_infohead info_head)
        {
            return (uint)((info_head.biHeight >> 1) * info_head.biWidth * (info_head.biBitCount>>3));
        }
        internal uint calc_bmp_and_len(struct_ico_dir_entry entry)
        {
            return (uint)((entry.height)* ((entry.width + ((32-(entry.width&31))&31))>>3));
        }
        internal int calc_bmpres_len(int width, int height)
        {
            return width * height * 4 + ((width >> 2) + ((width & 3) != 0 ? 4- (width & 3) : 0)) * height + 40;
        }
    }
}
